home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / oper_sys / quartz / quartz10.lha / doc / thread.ms < prev   
Text File  |  1990-05-16  |  77KB  |  1,704 lines

  1. .\" Two macros for code display --
  2. .\" Use fixed width font so indents show up right,
  3. .\" decrease point size and vertical spacing by two
  4. .de LS
  5. .br
  6. .DS B \\$1
  7. .\" Need courier font for code display
  8. .ft C
  9. .ps -2
  10. .vs -2
  11. ..
  12. .de LE
  13. .vs +2
  14. .ps +2
  15. .ft P
  16. .DE
  17. .br
  18. ..
  19. .EQ
  20. delim $$
  21. .EN
  22. .nr LL 6.5i
  23. .nr PO 1.0i
  24. .nr PI 0.2i
  25. .nh
  26. .nr PS 11
  27. .KS
  28. .sp 0.25i
  29. .KE
  30. .LP
  31. .ce 1000
  32. .LG
  33. .LG
  34. \fBThe Performance Implications of Thread Management Alternatives\fR 
  35. .sp 0.5
  36. \fBfor Shared-Memory Multiprocessors\fR
  37. .sp 1.5
  38. .SM
  39. Thomas E. Anderson, Edward D. Lazowska, and Henry M. Levy
  40. .sp 0.5
  41. .SM
  42. Department of Computer Science
  43. University of Washington
  44. Seattle WA  98195
  45. .sp 0.5
  46. February 1989
  47. .sp 2
  48. .LG
  49. \fBAbstract\fR
  50. .SM
  51. .ce 0
  52. .sp 0.5
  53. .PP
  54. .nh
  55. Threads ("lightweight" processes) have become a common element
  56. of new languages and operating systems.
  57. This paper examines the performance implications of several data 
  58. structure and algorithm alternatives for thread management in 
  59. shared-memory multiprocessors.  Both experimental measurements and
  60. analytical model projections are presented.
  61. .PP
  62. For applications with fine-grained parallelism, small differences in 
  63. thread management are shown to have
  64. significant performance impact, often posing a tradeoff between
  65. throughput and latency.  Per-processor data structures can be used 
  66. to improve throughput, and in some circumstances to avoid locking,
  67. improving latency as well.
  68. .PP
  69. The method used by processors to queue for locks is also shown 
  70. to affect performance significantly.  Normal methods of critical 
  71. resource waiting
  72. can substantially degrade performance with moderate
  73. numbers of waiting processors.  We present an Ethernet-style 
  74. backoff algorithm that largely eliminates this effect.
  75. .sp
  76. .LP
  77. .nh
  78. \fIIndex Terms\fR \- Multiprocessor, thread, locking, performance,
  79. parallel software, parallel computing.
  80. .FS
  81. .sp 0.5
  82. This material is based on work supported by
  83. the National Science Foundation
  84. (Grants No. CCR-8619663, CCR-8703049,
  85. and CCR-8700106),
  86. the Naval Ocean Systems Center,
  87. U S WEST Advanced Technologies,
  88. the Washington Technology Center,
  89. and Digital Equipment Corporation (the Systems
  90. Research Center and the External Research Program).
  91. .sp
  92. Authors' address:  Department of Computer Science FR-35,
  93. University of Washington, Seattle WA  98195; (206) 543-1695;
  94. \fItom/lazowska/levy@cs.washington.edu\fR.
  95. .FE
  96. .sp 2
  97. .LP
  98. .NH
  99. Introduction
  100. .nr H1 1
  101. .nr H2 0
  102. .PP
  103. .nh
  104. The purpose of this paper is to study the performance implications of
  105. thread management alternatives for shared-memory multiprocessors.
  106. .PP
  107. In traditional operating systems, a process, consisting of a single
  108. address space and a single thread of control within that address space,
  109. is used to execute a program.  Within the process, program execution 
  110. entails initializing and
  111. maintaining a great deal of state information.
  112. For instance, page tables, swap images, file descriptors, outstanding
  113. I/O requests, and saved register values are all kept on a per-program,
  114. and thus per-process, basis.  The sheer volume of this information
  115. makes processes expensive to create and maintain.
  116. .bp
  117. .PP
  118. Threads, or "lightweight" processes, separate the notion of 
  119. execution from the rest of the definition of a process.  A single 
  120. thread executes a portion of a program, cooperating with other threads 
  121. concurrently executing within the same address space.
  122. Like processes, every thread must have a separate program counter and stack
  123. of activation records, describing the state of its execution.
  124. However, much of what is normally kept on a per-process basis
  125. can be maintained in common for all threads executing in a single program,
  126. with dramatic reductions in overhead.
  127. .PP
  128. Thread packages have become a common element of new languages
  129. and operating systems for both uniprocessor and multiprocessor
  130. architectures.  Mach [Accetta et al. 1986], Topaz [Thacker et al. 1988], 
  131. Psyche [Scott et al. 1988], DYNIX [Sequent 1988], and several extensions
  132. to UNIX [Bach & Buroff 1984; Edler et al. 1988] are examples of 
  133. operating systems that provide explicit
  134. support for concurrent or parallel execution of programs.
  135. Ada [Mundie & Fisher 1985], CSP [Hoare 1978], Presto [Bershad et al. 1988a],
  136. Mesa [Lampson & Redell 1980], Concurrent Euclid [Holt 1982], and 
  137. Emerald [Jul et al. 1988] evidence equal interest within the language 
  138. community.
  139. .PP
  140. On uniprocessors, threads are used as a program structuring aid
  141. or to overlap I/O with processing.  The metric of goodness for
  142. these thread management implementations is simply processing cost per
  143. thread creation or context switch.  No locking is needed inside
  144. thread routines, since only one routine can be executing at any one time.
  145. .PP
  146. Programs on multiprocessors use threads to exploit parallelism.
  147. The speedup achievable by any given application
  148. depends on the availability of thread management routines
  149. that provide low cost facilities that are not a serial bottleneck.
  150. In Sequent's DYNIX operating system, for example, applications 
  151. must use normal UNIX-like processes for parallelism [Sequent 1988].  
  152. Since process creation in DYNIX takes over 25 milliseconds, only very 
  153. coarse-grained parallelism can be exploited.  As another example, the
  154. Topaz kernel provides relatively inexpensive thread creation and 
  155. synchronization,
  156. but the routines are protected by a single lock [Thacker et al. 1988].
  157. While this
  158. may be appropriate for architectures with small numbers of processors,
  159. as the number of processors increases, the single lock could limit
  160. speedups for applications with fine-grained parallelism.
  161. .PP
  162. Our initial experience in the area of high-performance thread packages
  163. was with Presto, an application-level runtime library that
  164. relies on the kernel only for processor allocation and memory 
  165. management [Bershad et al. 1988a].  This work showed that there is an
  166. order of magnitude performance advantage to using threads instead of
  167. DYNIX processes for exploiting parallelism.  Drawing on this experience, we 
  168. implemented a thread package that is, in turn, another order of magnitude
  169. faster than Presto.  This basic package was then modified
  170. to implement each alternative we wanted to explore.
  171. .PP
  172. One consequence of the speed of our basic thread package is that small 
  173. changes in the organization of data structures and locks have a significant 
  174. impact on performance.  Often, the choice involves a tradeoff between
  175. latency and throughput.  Per-processor data structures can sometimes 
  176. be used to avoid locking, however, improving latency and throughput 
  177. at the same time.
  178. .PP
  179. Another consequence of the speed of our thread package is that
  180. its performance depends noticeably on the algorithm used to queue for locks.
  181. Earlier, we studied the relative performance of spinning and blocking
  182. locks [Zahorjan et al. 1988].  In general, a thread that tries to 
  183. acquire a lock that is already held can either spin ("busy-wait")
  184. until the lock is released, or relinquish the processor.
  185. However, within the thread management routines themselves, spinning
  186. is the only option.  Thus, blocking at the user level may require 
  187. spin-waiting in thread management routines.
  188. Spin-waiting has a cost not only to the processor waiting 
  189. for the lock, but also to processors doing useful work.  The 
  190. degradation of other processors becomes substantial for moderate
  191. numbers of waiting processors, especially for small critical sections.
  192. We present an Ethernet-style backoff algorithm that largely 
  193. eliminates this effect.
  194. .PP
  195. The following sections describe these issues in more detail.
  196. In Section 2 we present an abstraction of a thread package:  its
  197. objects, resources, and operations.  Section 3 outlines the
  198. strategies for thread management that we examined and presents 
  199. measurements of their relative performance.  Section 4
  200. compares methods of queueing for locks.  Section 5 combines these 
  201. results in an analytical model.  Section 6 summarizes
  202. our experiences.
  203. .NH
  204. An Abstract Thread Package
  205. .PP
  206. As noted in Section 1, threads gain efficiency by separating the
  207. notion of execution from the rest of the definition of a process.
  208. The data structures needed by each thread are a program
  209. counter, a stack, and a control block.  (The control block contains state 
  210. information needed for thread management.  Through the control block,
  211. the thread can be put onto lists and other threads can synchronize with
  212. it.)  Another important data structure is the ready queue, which lists
  213. threads that are ready to run.  Lampson and Redell 
  214. [1980] provide a good description of
  215. the functionality of a uniprocessor thread package.
  216. .PP
  217. Thread operations are shown in Table 2.1.
  218. Creating a thread can be viewed as calling a procedure, except that 
  219. the callee can execute in parallel with the caller.  In both cases,
  220. the caller specifies a place to begin executing and some number of arguments.
  221. In fact, thread creation and startup is semantically equivalent to
  222. an asynchronous procedure call.
  223. .KF
  224. .TS
  225. center;
  226. l.
  227. Thread Creation
  228.         Allocate and initialize a control block, saving the initial PC.
  229.         Allocate a stack and copy in the thread's arguments.
  230.         Place the new thread on the ready queue.
  231. .sp .5
  232. Thread Startup
  233.         Remove the thread from the ready queue and begin to execute it.
  234. .sp .5
  235. Thread Block (wait on a blocking lock, monitor condition variable, or message)
  236.         Save register values and PC on the thread's stack.
  237.         Place the thread on the condition queue for the event.
  238.         Look for a thread in the ready queue, and start or resume it.
  239. .sp .5
  240. Signal a Blocked Thread
  241.         Remove the thread from the condition queue.
  242.         Place the thread on the ready queue.
  243. .sp .5
  244. Thread Resume
  245.         Remove the thread from the ready queue.
  246.         Restore registers.
  247.         Continue executing it from the saved PC.
  248. .sp .5
  249. Thread Finish
  250.         Deallocate the stack and control block.
  251.         Look for a thread in the ready queue, and start or resume it.
  252. .TE
  253. .ce
  254. \fBTable 2.1:  Thread operations\fR
  255. .KE
  256. .PP
  257. As Table 2.1 shows, a program can create a thread even if there is no 
  258. idle processor available to run it.  Because the parallelism cannot 
  259. be immediately exploited in this case, it might seem that the overhead 
  260. of thread creation should be avoided.  The program may run faster by 
  261. creating the thread, however, if at some future time there will be an 
  262. idle processor that can be used to execute the thread.
  263. This idea of creating parallelism for future use is very powerful.
  264. Unfortunately, in the above framework, its space cost is prohibitive.
  265. Each thread must be initially allocated a large amount of space for
  266. its stack, since it is expensive to dynamically expand the space if 
  267. the thread later runs out of it.  In Table 2.1, the thread is allocated 
  268. space for a stack when it is created, but the space is largely wasted 
  269. until the thread is actually started.  Using virtual memory could remove
  270. the need to allocate physical memory to back the stack space until the
  271. thread begins to run; however, allocating extra virtual memory is itself
  272. expensive.
  273. .PP
  274. An important optimization to Table 2.1, therefore, is to copy a thread's
  275. arguments into its control block when the thread is created.  This way,
  276. the stack need not be allocated until thread startup; the arguments
  277. can be copied from the control block to the stack at that time.
  278. WorkCrews [Vandevoorde & Roberts 1988] and Presto 
  279. [Bershad et al. 1988a] both take this approach.
  280. .PP
  281. Another important optimization is to store deallocated control blocks and 
  282. stacks in free lists [Bershad et al. 1988a].  If these data structures were
  283. individually allocated out of the heap, thread overhead would include
  284. the cost of finding a free block of the correct size as well as
  285. possibly coalescing the block when it is returned to the heap.
  286. By using free lists, both allocation and deallocation can normally
  287. be simple list operations.
  288. .PP
  289. We begin our study by assuming these optimizations.  For simplicity, we will
  290. focus on the effect of thread management alternatives on the performance
  291. of only a few thread operations:  creation, startup, and finish.
  292. These operations manipulate each of the three shared data
  293. structures:  the ready queue,
  294. the stack free list, and the control block free list.
  295. Most of the discussion applies as well to the peformance of block 
  296. and resume operations.
  297. .NH
  298. Thread Management Alternatives
  299. .PP
  300. In a parallel environment, access to shared data structures must be
  301. serialized to ensure consistency and correctness.  Our thread package uses
  302. spinlocks for this purpose:  when a processor tries to modify a data
  303. structure, it must first lock it to obtain exclusive access; if
  304. some other processor already holds the lock, the processor loops until
  305. the lock is released.
  306. .PP
  307. Locking implies dual concerns of latency and throughput 
  308. [Kumar & Gonsalves 1977].  Latency
  309. is the cost of thread management under the best case assumption
  310. of no contention for locks.  Throughput, on the other hand, is the
  311. rate at which threads can be created, started, and finished when there 
  312. is contention.  If part of
  313. thread management must be done serially, then no matter how many processors
  314. work on a problem, there will be some maximum rate at which thread operations
  315. can be performed.
  316. .PP
  317. There are several ways of defining latency, with different
  318. implications for different types of applications.  If an application
  319. keeps all of its processors continually busy, for instance by creating
  320. threads before they are needed, then any time spent in creating,
  321. starting, or finishing a thread is time that could have been spent doing
  322. other useful work.  When a thread finishes, however, if there is no other work
  323. for the processor to do, the time spent deallocating the thread's data 
  324. structures is unimportant.  Instead, the relevant issues include 
  325. how much a creating processor is delayed, since it has a thread to
  326. run, and how much time it takes for the created thread to begin
  327. running on a processor.
  328. .PP
  329. In the following subsections, we define five alternative thread
  330. management strategies, and describe some of the potential advantages 
  331. and disadvantages
  332. of each approach.  We then provide measurement and analytical
  333. comparisons of these alternatives.
  334. .NH 2
  335. Single lock:  central data structures protected by a single lock
  336. .PP
  337. The most obvious approach to thread management is to protect all
  338. data structures under a single lock.  Once the lock is acquired
  339. by a processor, the processor is assured that it can modify any stored state.
  340. To perform a thread operation, a processor must first acquire the lock,
  341. then do what is needed to the shared data, and finally release the lock when
  342. done.  In this way, only a single lock is needed per thread operation,
  343. but, since most of 
  344. the thread management path is serialized, throughput is limited.
  345. In the typical scheme, idle processors loop checking the ready queue for 
  346. work to do,
  347. causing useless contention for the ready queue lock; however, this can 
  348. be avoided if idle processors check that the ready queue is not empty before 
  349. acquiring the lock.
  350. (Ni and Wu [1985] present a different approach.)
  351. .NH 2
  352. Multiple locks:  central data structures each protected by a separate lock
  353. .PP
  354. A somewhat more modular approach to locking is to separately 
  355. protect each data structure with its own lock [Lampson & Redell 1980].
  356. Each operation on the data structure can then be surrounded by
  357. a lock acquisition and release.  For thread management, this involves
  358. separately locking each enqueue and dequeue operation on the ready 
  359. queue, stack free list, and control block free list, the three shared data 
  360. structures.
  361. .PP
  362. There is a basic tradeoff between latency and throughput in the
  363. choice between using a single lock or multiple locks in protecting
  364. shared data structures [Kumar & Gonsalves 1977].  Since less of the total 
  365. thread activity is in a critical section, and since it is split among
  366. several locks, the maximum rate of thread operations is higher with multiple
  367. locks than
  368. with a single lock.  There is a cost to this increased throughput,
  369. however:  more lock accesses are needed, increasing latency.
  370. .NH 2
  371. Local freelist:  per-processor free lists without locks; a central
  372. locked ready queue
  373. .PP
  374. One way of avoiding locking is to maintain as much state as possible
  375. locally, with each processor.  If each processor maintains its own 
  376. free lists of control blocks and stacks, these structures need not 
  377. be locked, since only one processor will ever access them.  As
  378. before, there is a single shared ready queue whose accesses are locked.
  379. .PP
  380. The tradeoff between latency and throughput can be largely avoided
  381. by using local free lists.  Since fewer lock acquisitions are needed 
  382. per thread, latency is lower than with multiple locks, yet since only 
  383. accesses to the ready queue are serialized, throughput is better.
  384. .PP
  385. Local free lists need to be balanced.  Control blocks
  386. and stacks can migrate between free lists if the thread is created or 
  387. started on one processor and finished on another.  This can happen, for 
  388. instance, if 
  389. a thread blocks and is resumed on a different processor.  Thus, one free 
  390. list can be empty, requiring the processor to obtain more space from 
  391. the heap, while another free list has many entries.  In the worst case,
  392. some processors only create and start threads (allocate structures), 
  393. while other processors only finish them (deallocate structures).
  394. Without balancing, the deallocated structures are never re-used;
  395. a separate stack and control block are needed for every thread.
  396. In contrast, with a centralized free list, only as many are needed 
  397. as there are active (created or started, but not finished) threads.
  398. .PP
  399. It is inexpensive, however, to balance free lists by using a 
  400. global pool and a threshold $T$ on the maximum size of each list.  When  
  401. the size of a free list reaches the threshold, half the list can be 
  402. returned to the global pool; when a free list empties, $T / 2$
  403. entries can be claimed from the pool.  The global pool must be locked, 
  404. of course.  For efficiency, it can be organized as a list of lists.  The 
  405. processing cost to balancing is thus one locked pool access amortized
  406. across at least $T / 2$ free list accesses.
  407. Let $P$ be the number of processors.  An application using balanced
  408. local free lists will use no more than $O(P times T)$ more space 
  409. than one using a central list, even if one processor only allocates
  410. structures that other processors deallocate.  The worst case occurs when the
  411. allocating processor's free list and the global pool are empty even 
  412. though the other local free lists are almost full.
  413. .PP
  414. Thus, local free lists trade space for time.  This tradeoff
  415. is practical for control blocks.  Utilization
  416. of the pool lock is at most $O( P times {R over T} )$, where $R$ is the rate of 
  417. thread creation on a single processor.  To ensure that the pool lock
  418. is not a source of contention (which would inflate the overhead per 
  419. free list access), we can set the threshold $T$ to be equal to $P$.
  420. Control blocks are relatively small objects (in our implementation,
  421. roughly $100$ bytes); provided $P$ is not excessively large, using
  422. $100P$ bytes per processor is not onerous.  If $P$ is large,
  423. then a tree of pools could be used to limit the cost to balancing to
  424. $O(P / log~P)$ bytes per processor. 
  425. .PP
  426. The tradeoff is not practical for stacks, however.
  427. Stacks are at least two orders of magnitude larger than 
  428. control blocks.  Even if sufficient memory were available, using
  429. that memory entails processing costs for initializing page tables
  430. and increased cache miss rates that could easily overwhelm the 
  431. advantage gained from decreased locking.  Instead, we let the local
  432. stack free lists contain at most one element.
  433. In this way, stacks need be allocated 
  434. from the global pool only when a processor blocks a thread and then
  435. starts up a different thread, and deallocated only when a processor 
  436. finishes a thread and then resumes another thread.
  437. .NH 2
  438. Idle queue:  a central queue of idle processors; per-processor free lists
  439. .PP
  440. None of the algorithms described so far exploit parallelism
  441. in creating threads.  The creating processor allocates and initializes
  442. the control block; only when it is done is the starting processor allowed
  443. to allocate and initialize the stack.
  444. The cost of thread creation could be reduced if some of the work
  445. was done by idle processors in parallel with the creating processor.
  446. .PP
  447. In addition to a central queue of threads, we can maintain a
  448. central queue of idle processors.  When there is a backlog of ready
  449. threads, there is no point to attempting parallel thread creation since
  450. all processors are already doing useful work.  When a processor becomes
  451. idle and there is no backlog, it pre-allocates a control block and stack,
  452. puts itself on the idle queue, and spins on a local flag waiting for work.
  453. Thread creation then dequeues the idle processor, initializes the 
  454. pre-allocated control block and stack, and sets that processor's flag, 
  455. indicating that it now has a thread that is ready to run.  Instead of 
  456. processors searching for work, work searches for processors.
  457. .PP
  458. In fact, this approach does not alter the essentially sequential
  459. nature of thread creation.  The idle processor must first queue itself
  460. before the creating processor can dequeue it, which in turn must
  461. set the flag before the idle processor can start running the thread.
  462. The critical path between the beginning of thread creation and when 
  463. the thread starts running is reduced by doing some of the work 
  464. (allocating structures, acquiring a lock, enqueueing) 
  465. before the critical path begins.
  466. Since this adds complexity, and there is no benefit in the absence of idle 
  467. processors,
  468. the effect is to trade off reduced latency when there are idle processors for 
  469. increased latency when all processors are busy.  Maximum throughput should be
  470. unchanged since two locked queue operations are still needed per thread
  471. life cycle.  Wagner et al. [1989] describe a different way of using of 
  472. idle processors to avoid work during blocking and resuming.
  473. .NH 2
  474. Local readyq:  per-processor ready queues; per-processor free lists
  475. .PP
  476. Once free lists are made local, the ready or idle queue lock can become a 
  477. serial bottleneck as the number of processors or the rate each processor
  478. schedules work increases [Dritz & Boyle 1987].
  479. One way of increasing throughput is to
  480. divide the load on a single lock among several locks.  An application
  481. of this idea is to keep a ready queue per processor.  In this way,
  482. enqueueing and dequeueing threads can occur in parallel, with
  483. each processor using a different queue.  There is again a tradeoff 
  484. between latency and throughput in the choice between using one or more 
  485. ready queues.
  486. .PP
  487. Unlike the case of control block free lists, unlocked local ready queues
  488. are inefficient even if balanced through a global pool.
  489. Runnable threads are a scarce resource.
  490. An idle processor might have an empty queue, yet a ready thread that 
  491. the processor could run is in some other processor's queue, while the global
  492. pool is empty.  Performance can be arbitrarily bad
  493. in any scheme where a processor can be idle indefinitely while there is even
  494. one ready thread in some other queue.  For instance, suppose $P$ identical 
  495. threads are created, but due to an imbalance, only $P~-~1$ are started
  496. while one processor idles.  The runtime would then be twice as long
  497. as with any of the centralized queueing strategies.
  498. .PP
  499. One simple way of avoiding indefinite idling is to lock each ready queue;
  500. each idle processor can then scan the ready queues for work, 
  501. beginning with its own [Dritz & Boyle 1987].
  502. If there is a ready thread, an idle processor will 
  503. eventually find it.  Processors can queue created threads locally, since 
  504. balancing is achieved by idle processors.  However, contention can still 
  505. occur if a single processor enqueues (during create or signal) every 
  506. ready thread; that processor's queue
  507. would operate as if it was a central ready queue, except that
  508. idle processors would have to waste time scanning for it.  A simple
  509. way of avoiding this situation is to randomly choose a queue for 
  510. each newly ready thread.
  511. .PP
  512. If each queue is equally likely to get a new ready thread, latency is
  513. bad when the number of runnable threads is near to the number of processors.
  514. There are two cases.  Consider the cost of scheduling a thread onto
  515. a newly idle processor.  If there are no ready threads, there is effectively
  516. no cost until a new thread is created.  If there are ready but not running
  517. threads, any time spent finding a thread to run could have been spent 
  518. running that thread.  This time is small when there are many ready 
  519. threads, because the idle processor will find the thread after scanning 
  520. only a few queues; however, when there
  521. is only a single ready but not yet running thread, the processor will
  522. have to examine on average half of the queues in order to find it.
  523. The cost of scheduling a newly created thread onto an idle processor
  524. is similar: the thread will be found quickly if there are many 
  525. idle processors and more slowly if there are only a few.
  526. .PP
  527. One reason to have a one-to-one correspondence between processors 
  528. and ready queues is to maintain locality.  There is an application-specific
  529. cost to migrating a newly resumed thread from the processor it last ran
  530. on, due to increased cache misses.  The ability to avoid migration
  531. depends on the backlog of ready threads [Eager et al. 1986].
  532. .PP
  533. If maintaining locality is not important, then there is a tradeoff
  534. between latency and
  535. throughput in choosing the number of queues [Ni & Wu 1985].
  536. Up to some maximum, throughput is higher with more queues, but 
  537. the number of queues that must
  538. be scanned to find work, and thus the latency, is also higher.
  539. We set the number of queues equal to the number of processors for 
  540. all measurements.
  541. .NH 2
  542. Measurement results
  543. .PP
  544. To validate our intuitions about the relative merits of the alternative
  545. approaches, we implemented each on a Sequent Symmetry Model A shared-memory 
  546. multiprocessor.  All code was written in C and compiled with Sequent's
  547. standard compiler, with the exception of the locking and context
  548. switching code, which was programmed in assembler.
  549. Our Symmetry has twenty Intel 80386 processors, a shared bus,
  550. and a write-through cache coherency protocol [Lovett & Thakkar 1988].
  551. The Symmetry has a timer with microsecond resolution that was used for 
  552. all measurements.  Table 3.1 contains times for sample Symmetry operations.
  553. .KF
  554. .TS
  555. center, box;
  556. c c
  557. l n.
  558. Operation    Runtime (\(*msec.)
  559. _
  560. Acquire and release a lock    5.6
  561. Procedure call with no arguments    3.6
  562. Each 4-byte argument    1
  563. Iteration of null loop    2.5
  564. .TE
  565. .ce
  566. \fBTable 3.1:  Runtimes for Symmetry operations (measured)\fR
  567. .KE
  568. .PP
  569. For all measurements, free lists were "warm started":  sufficient control 
  570. blocks and stacks were pre-allocated for use by the benchmark.  Our 
  571. purpose was to measure the relative merits of each alternative, rather 
  572. than the efficiency of the underlying memory management system.  The 
  573. cache was not warm-started, but we ran each benchmark long enough
  574. for this effect to become insignificant.
  575. .PP
  576. Figure 3.1 is the principal performance comparison:  it shows the elapsed 
  577. time in seconds for each thread management alternative to create,
  578. start, and finish one million "null" threads, for varying numbers of
  579. processors.  The one processor case shows the latency for a single thread in 
  580. microseconds when there is no contention for locks.
  581. .PP
  582. Table 3.2 lists the code for this test.
  583. Initially, $P$ threads are created; each recursively creates
  584. a thread then finishes, allowing that processor to start up one of the
  585. waiting threads.  The test terminates when each processor has executed
  586. 1 million / $P$ threads; in practice, we found that the processors all 
  587. completed at roughly the same time.
  588. For the multiple ready queue alternative, each 
  589. newly created thread was added to a random queue to avoid biasing the results
  590. with the effect of locality.  This test is not intended to be representative 
  591. of a real parallel program, but it does expose the tradeoffs between the five
  592. alternatives.  ("numberOfThreads" is a location private to each processor,
  593. initially set to 0.)
  594. .KF
  595. .ft C
  596. .ps -1
  597. .vs -1
  598. .TS
  599. center;
  600. l.
  601. ThreadCycle () {
  602.   numberOfThreads = numberOfThreads + 1;
  603.   if (numberOfThreads == 1000000 / numberOfProcessors)
  604.     Synchronize(); /* wait for all processors to reach here */
  605.   else
  606.     ThreadCreate(ThreadCycle);
  607. }
  608.  
  609. BeginTimer();
  610. for (i = 1; i < numberOfProcessors; i++)
  611.   ThreadCreate(ThreadCycle);
  612. ThreadCycle();
  613. StopTimer();
  614. .TE
  615. .ps +1
  616. .vs +1
  617. .ft P
  618. .ce
  619. \fBTable 3.2:  Benchmark: create, start, and finish 1 million null threads\fR
  620. .KE
  621. .PP
  622. Figure 3.2 shows the inverse graph for the same test: speedup as a 
  623. function of the number of processors.  We define speedup to be the 
  624. ratio of the time for the the fastest alternative on one processor
  625. (single lock) to the time each alternative takes on $P$ processors.
  626. Speedup is proportional to throughput; this graph shows the maximum
  627. number of thread creates, starts, and finishes that can be performed 
  628. in parallel for each alternative.
  629. .KF
  630. .sp 3.0i
  631. .ce 2
  632. \fBFigure 3.1:  Principal results for thread management \- elapsed time
  633. to create, start and finish 1,000,000 null threads (measured)\fR
  634. .sp .5
  635. .sp 3.0i
  636. .ce
  637. \fBFigure 3.2:  Speedup to create, start and finish 1,000,000 null threads (measured)\fR
  638. .sp .5
  639. .KE
  640. .PP
  641. Before examining the relative performance of the five alternatives,
  642. we note that each of them has quite good performance.
  643. Threads are 
  644. only an order of magnitude more expensive than a procedure call, and
  645. 500 times less expensive than normal DYNIX processes.
  646. Threads in Presto cost 600 microseconds on the 
  647. same Symmetry hardware, an order of magnitude worse than our threads
  648. although an order of magnitude better than DYNIX processes.
  649. .PP
  650. While Presto's speedup relative to DYNIX is due to using threads instead
  651. of processes, our speedup relative to Presto is due to attention to
  652. implementation details.  We implemented Presto in C++; while this enhanced
  653. its ability to be modified [Bershad et al. 1988b], its C++ was first 
  654. pre-processed into C, then compiled.  This resulted in much less efficient 
  655. code than could be achieved by direct coding in C.  Another factor is that
  656. we stripped thread control blocks of all non-essential state, reducing
  657. the cost of initialization dramatically.  We did not remove
  658. functionality:  our thread package
  659. could be given Presto's user interface without 
  660. sacrificing its performance.
  661. .PP
  662. Because our threads are inexpensive, the choice of alternatives has a 
  663. large relative impact on both latency and throughput for applications with
  664. fine-grained parallelism.
  665. Specifically:
  666. .IP \(bu
  667. Adding even a single lock acquisition into the thread management
  668. path can increase latency significantly.  Locking each of the data 
  669. structures separately results in a much higher latency than 
  670. locking all data structures under the same lock.  Using 
  671. per-processor data structures to avoid locking
  672. is thus crucial to decreasing latency without sacrificing throughput.
  673. .IP \(bu
  674. Additional complexity results in a noticeable increase in latency.
  675. There are on the order of 100 instructions in the thread management
  676. path; adding even a few extra instructions impacts performance.
  677. For example, the idle queue strategy checks for idle processors on
  678. thread creation.  If the idle queue is always empty, as in the measurements 
  679. in Figure 3.1, it defaults to a normal ready queue.
  680. Even this simple check markedly increases the cost of threads.  
  681. This implies that thread management routines must be kept simple;
  682. enhancements that would otherwise seem plausible but add complexity 
  683. are unlikely to work, since there is little computation
  684. to save, and it is easy to swamp the savings with increased overhead.
  685. .IP \(bu
  686. A large portion of the thread management path is locked, since
  687. little work is required beyond manipulation of shared data.  When all data
  688. is kept under a single lock, throughput is limited by
  689. contention for this lock.  However, even with local free lists,
  690. the lock on the ready queue limits throughput to a few concurrent
  691. thread operations.  Only local ready queues can support
  692. higher throughput.
  693. .IP \(bu
  694. When lock contention is not a problem, the bandwidth of the bus
  695. limits thread management throughput.  The curve in Figure 3.2 levels 
  696. out for the local ready queue alternative, even though there is 
  697. no significant contention for locks.  While the high bus demand per
  698. thread may be specific to the write-through cache protocol on the Symmetry,
  699. bus contention is likely to be a problem on any bus-structured shared-memory
  700. system.
  701. .PP
  702. In Figures 3.1 and 3.2, threads do no work except to create other threads.
  703. It is natural to ask whether the performance implications of the thread
  704. management alternatives would still be significant in the presence
  705. of user-mode computing.  We modified the test in Table 3.2 so that each
  706. thread performs an average of 300 microseconds of user work, taken from a 
  707. uniform distribution.  This simulates the behavior of an application
  708. with fine-grained parallelism.
  709. .PP
  710. Figure 3.3 graphs speedup for this modified benchmark.  Differences appear 
  711. as the number of processors increases.
  712. .KF
  713. .sp 3.0i
  714. .ce
  715. \fBFigure 3.3:  Speedup, user work = 300 \(*msec. (measured)\fR
  716. .sp .5
  717. .KE
  718. .PP
  719. Figure 3.4 graphs thread cost in microseconds as a function of the number of 
  720. runnable threads (parallelism).
  721. Thread cost was directly measured by taking timestamps
  722. before and after each thread was created and whenever a thread was started
  723. or finished.  Multiple creations and completions were measured and averaged to
  724. improve accuracy; they were synchronized to avoid measuring lock contention.
  725. .KF
  726. .sp 3.0i
  727. .ce
  728. \fBFigure 3.4:  Thread latency (\(*msec.) vs. number of runnable threads, 18 processors (measured)\fR
  729. .sp .5
  730. .KE
  731. .PP
  732. There are a few things to note in Figure 3.4:
  733. .IP \(bu
  734. The curve for the central ready queue alternative jumps
  735. when the number of runnable threads reaches the number of processors
  736. because of a change in the definition of thread latency.
  737. When there are fewer threads than processors, thread cost is taken to be 
  738. the time to create and start running a new thread.  The time to finish 
  739. a thread is unimportant if the idling processor has no work to do.
  740. When there are as many or more runnable threads as processors, the cost is 
  741. the sum of the time
  742. to create a thread plus the time to finish it and start a new thread.
  743. The thread latency reported in Figure 3.1 with
  744. one processor corresponds closely to the latency reported in Figure 3.4
  745. when there are more runnable threads than processors.
  746. .IP \(bu
  747. Using an idle queue is faster when there are idle processors, 
  748. but slower when there are more runnable threads
  749. than processors.  Thread creation is faster if an idle processor can be
  750. used to do work before the thread is created, but checking the idle
  751. queue incurs overhead even if it is not used.  Whether a particular
  752. application will run faster with an idle queue depends on how much
  753. time it spends in each case.
  754. .IP \(bu
  755. The spike in the curve for per-processor ready queues shows that
  756. finding a ready thread among many queues is
  757. expensive when the parallelism of the application is near to the 
  758. number of processors, but the expense fades when more
  759. ready threads or more idle processors are available.  Since the 
  760. height of the spike grows linearly with the number of queues, 
  761. latency increases in proportion to the maximum throughput.
  762. However, the amount of user computing per thread needed to avoid 
  763. contention for a central ready queue also grows linearly with the number
  764. of processors.  Thus, the cost of searching for work among per-processor 
  765. ready queues as a fraction of the time to do that work is not large unless
  766. the multiple ready queues are needed for additional throughput.
  767. .PP
  768. One area of further research is to examine hybrid thread management
  769. strategies to combine the advantages of some of the alternatives we
  770. have presented.  For example, a per-processor version of the central
  771. idle queue could exploit locality and parallelize thread creation 
  772. without compromising throughput.  As another example,
  773. both central and per-processor ready queues could be used, by
  774. placing threads in a local queue if the lock on the central queue is
  775. busy.  The drawback to any such approach is that complexity adds cost
  776. which may outweigh any benefits.
  777. .NH 2
  778. Analytical explanation of Figure 3.4
  779. .PP
  780. We now derive a formula that explains in detail the spike
  781. for the per-processor ready queue alternative in Figure 3.4.
  782. When there are idle processors, we need to know the time between the
  783. queueing of a ready thread and the dequeueing of that thread by an
  784. idle processor; when there is a backlog of ready threads, we need to 
  785. know how long it takes a newly idle processor to find one of the threads.
  786. .PP
  787. Let $E(r,q)$ be the expected number of 
  788. queues examined by a newly idle processor to find one of $r$ ready threads,
  789. which are randomly distributed among $q$ queues.  Without loss of
  790. generality, let the queues be numbered from $1$ to $q$, let threads
  791. be numbered from $1$ to $r$, let $i sub j$ be the queue containing the 
  792. $j$th thread, and let the idle processor
  793. begin searching with queue 1.  The idle processor must examine the number
  794. of queues equal to the lowest numbered non-empty queue.
  795. The number of ways of putting $r$ 
  796. threads into $q$ queues is $q sup r$.
  797. .br
  798. .EQ I
  799. E(r,q)~=~
  800. {1 over q sup r}~
  801. sum from {i sub {1}~=~1} to {q} 
  802. ~{sum from {i sub {2}~=~1} to {q} 
  803. ~{... sum from {i sub {r}~=~1} to {q}
  804. {~minimum~of~(i sub {1},~i sub {2}, ... i sub {r})}}}
  805. .EN
  806. .br
  807. We can separately sum when each $i sub j$ is the minimum.  When
  808. more than one thread is at the minimum, we count the value once
  809. in the sum for the least numbered thread.  Thus, the value of $i sub j$
  810. is counted whenever for all $k < j$, $i sub k > i sub j$, and for
  811. all $k > j$, $i sub k >= i sub j$.  There are ${(q - {i sub j})} sup {j-1}$
  812. values for the $i sub k$, $k < j$ that satisfy the first condition
  813. and ${(q - {i sub j} + 1)} sup {r-j}$ 
  814. values for the $i sub k$, $k > j$ that satisfy the second.
  815. .br
  816. .EQ I (3.1)
  817. E(r,q)~=~
  818. {1 over q sup r}~
  819. left (~q~+~sum from {j = 1} to {r} ~{sum from {i = 1} to {q-1} 
  820. {i {(q - i)} sup {j-1} {(q - i + 1)} sup {r-j}}} right )
  821. .EN
  822. .PP
  823. By symmetry, Equation 3.1 also holds when there are more processors
  824. than runnable threads.  Let $r$ be the number of idle processors,
  825. let $i sub j$ be the queue currently scanned by the $j$th idle 
  826. processor, and let the newly created 
  827. thread be put into queue $1$.  Then the processor that actually dequeues
  828. the thread will have to look through $E(r,q)$ queues, after the
  829. thread is queued, in order to find it. 
  830. .PP
  831. Figure 3.5 graphs Equation 3.1 for 18 processors.  In order to correspond
  832. to Figure 3.4, the x-axis is the number of runnable threads, rather than 
  833. the number of ready but not running threads or the number of idle processors.
  834. Since part of the spike in Figure 3.4 is due to the difference
  835. in the measurements when there are idle processors or not, the
  836. curves in Figures 3.4 and 3.5 correspond well.
  837. .KF
  838. .sp 3.0i
  839. .ce
  840. \fBFigure 3.5:  Queues examined vs. number of runnable threads, 18 processors (Equation 3.1)\fR
  841. .sp .5
  842. .KE
  843. .PP
  844. The above analysis assumes that events occur one at a time.  Since
  845. finding a ready thread among a number of queues can take a non-trivial
  846. amount of time, it is reasonable to consider what happens when another 
  847. thread is created or another processor becomes idle during the interim.
  848. Suppose another thread is created before a newly idle processor finds one
  849. of the $r$ ready threads.  Let $C$ be the cost (in number of queues
  850. examined) of finding a thread in this situation.  $C$ can be no better than
  851. if the new thread had been there all along and no worse than if the new
  852. thread is ignored.  In other words, $E(r+1,q)~<=~C~<=~E(r,q)$.
  853. Similarly, if another processor becomes idle in the interim, provided 
  854. $r~>=~2$, the combined cost for both processors to find threads is 
  855. $E(r,q)~+~E(r-1,q)$, assuming the processors do not contend for the same
  856. queue, independent of which processor finds a ready thread first.
  857. .NH
  858. Spinlock Management Alternatives
  859. .PP
  860. If a processor finds a thread management lock busy, it spin-waits
  861. for the lock to be released.  An alternative would be to block, relinquishing
  862. the processor to do other useful work while the lock is busy.
  863. However, since thread management locks are held for only a short time,
  864. the overhead of performing this context switch would be prohibitive;
  865. in addition, any other work that the processor might do is controlled
  866. by a (or possibly the same) thread management lock.
  867. When an application lock is busy, a thread does have a choice between 
  868. spin-waiting or blocking, but blocking at the user level may result 
  869. in spin-waiting in a thread routine.
  870. .PP
  871. Spin-waiting has a hidden cost.  Processors doing useful work may be
  872. slowed by processors that are merely waiting for a lock, due to bus
  873. contention.  As a result,
  874. adding to the number of processors executing an application may
  875. in fact slow it down by increasing the average number of spinning
  876. processors.  Worse, the more spinning processors, the more the
  877. processor holding the lock is slowed, increasing the effective
  878. size of the critical section, resulting in even more waiting processors.
  879. .PP
  880. In this section, we evaluate three different approaches to spin-waiting.
  881. .NH 2
  882. Hardware description
  883. .PP
  884. On the Symmetry Model A, each processor has its own cache; provided 
  885. all of its memory references can be satisfied out of that cache, 
  886. a processor's progress is independent of the activity of other processors.  
  887. Whenever a processor reads data that is not in its cache, it must wait for 
  888. the data to come from memory via the bus; with a write-through protocol,
  889. a processor may also have to wait for writes to be sent to memory.
  890. In both cases, the wait can be longer and the processor's progress slowed
  891. because of bus contention.
  892. .PP
  893. The Symmetry has a basic test-and-set instruction, xchgb (exchange byte),
  894. that atomically reads a memory location and writes in a new value.
  895. The atomicity of the xchgb operation is enforced by the bus:  a copy of 
  896. the memory location is brought into the processor's cache, modified there,
  897. and then written back to memory.  As the value is written to memory, 
  898. all copies of the old memory value in other caches are invalidated.
  899. No comparison of the old and new values is performed; memory is written
  900. and other copies invalidated even if the value is unchanged.
  901. Any requests for
  902. that memory location in the interim are delayed until the processor 
  903. is done modifying it [Lovett & Thakkar 1988].
  904. .PP
  905. The Sequent locking protocol is as follows:  The lock is held if the value
  906. is a 1 and free if 0.  To lock, a processor exchanges 
  907. in a 1.  If the old value was a 0, it got the
  908. lock; if the value was a 1, the lock was already held by someone else,
  909. and the processor must try again.  In either case, the value
  910. is 1 afterwards.  The lock is released by exchanging in a 0; this
  911. allows some other processor to get a 0 back in exchange for a 1.
  912. There are several potential protocols for spin-waiting, which are
  913. described below.
  914. .NH 2
  915. Spin on xchgb
  916. .PP
  917. Perhaps the simplest way to implement spin-waiting is for each processor
  918. to loop on the xchgb instruction until it succeeds.  The drawback
  919. to this approach is that every xchgb instruction consumes bus
  920. resources, whether or not it succeeds.
  921. As additional processors spin on the lock, the holder 
  922. of the lock is slowed both because the bus is busier and because to 
  923. free the lock it must contend for permission to update the lock
  924. value with the xchgb's of processors uselessly trying to acquire the lock.
  925. .NH 2
  926. Spin on read
  927. .PP
  928. Coherent caches seem to allow processors to spin without using bus cycles. 
  929. A processor can try to acquire the lock once; if this fails, the 
  930. processor can spin reading the lock memory location.  As long as the 
  931. value is 1, the lock is still held.  This spinning is done in the
  932. the cache, avoiding bus traffic.  When the lock
  933. is released, the cache copy will be invalidated; the spinning processor
  934. will see the value change to 0 and can then try to acquire the lock
  935. using an xchgb operation.
  936. Sequent's runtime library uses this implementation [Sequent 1988].
  937. .PP
  938. A problem arises when there are a number of processors waiting for a
  939. small critical section.  When the lock is freed, every spinning
  940. processor's cache copy is invalidated, causing each processor to fetch
  941. the new value in turn.
  942. The first to try to acquire the lock succeeds; however, each processor
  943. that sees the value as 0 before this occurs will also, in turn, try to 
  944. acquire the lock, fail, and go back to looping on a read.  Unfortunately, each 
  945. processor that does an unsuccessful xchgb operation invalidates all 
  946. cache copies, forcing all processors that were looping to miss again.
  947. Thus, after each such operation, almost every spinning processor contends
  948. for the bus, some still waiting to do an xchgb and the rest to fetch
  949. the lock value.  Eventually, each processor sees that the lock has been
  950. acquired and quiesces, looping in its cache.
  951. .PP
  952. For a given number of spinning processors,
  953. the performance of this algorithm is better for longer critical sections.
  954. After the lock is released and before quiescence, 
  955. each spinning processor spends most of its time with a pending bus request;
  956. any normal bus request during this time will be correspondingly delayed.
  957. After quiescence, the spinning processors  
  958. place no load on the bus, allowing the processor
  959. holding the lock to progress unhindered.  With longer critical
  960. sections, the initial degradation is less significant.
  961. By contrast, spinning on the xchgb instruction degrades bus performance
  962. evenly throughout the critical section.
  963. .NH 2
  964. Ethernet-style backoff
  965. .PP
  966. The source of the difficulty is that there is a cost to attempting
  967. to acquire the lock.  A generic solution to problems
  968. of this sort is to have each processor estimate its likelihood
  969. of success, and only try the lock when the probability is high.  The
  970. estimate can be made from experience.  The more times a processor
  971. has tried and failed, the more likely it is that many processors
  972. are spinning for the lock.  When the lock is released, then, instead
  973. of every processor rushing to try to get it, each waits a period 
  974. of time dependent on the number of past failures.  If the lock is
  975. still free after this period, then the probability of success is
  976. high enough to try the lock.  We used this algorithm for our
  977. measurements in Section 3. 
  978. .PP
  979. The analogy with Ethernet is revealing.  In the Ethernet protocol,
  980. a processor can start a network transmission in any time slot that
  981. the network is free [Metcalfe & Boggs 1976].  If two try to start 
  982. transmitting in the same slot, both fail and must be retried later.
  983. To avoid further collisions,
  984. the length of time before retrying depends on the number of
  985. collisions encountered so far.  In our case, when a number of
  986. processors simultaneously try to acquire a lock, one will succeed,
  987. but its progress will be slower than if there were no collisions.
  988. .PP
  989. The downside to Ethernet-style protocols is that they are unfair.
  990. A processor that has just arrived is more likely to acquire the
  991. lock (or network) than one who has been waiting, and failing, for
  992. some time.  Spinning on a test-and-set instruction and spinning
  993. on a read of the lock location are both probabilistically fair; each
  994. spinning processor has an equal likelihood of getting the lock,
  995. even though the possibility of indefinite starvation exists.
  996. Lock fairness is sometimes important to an application.
  997. .PP
  998. Another drawback of the backoff algorithm is that it takes longer for a 
  999. spinning processor to acquire a newly free lock.  The processor must check 
  1000. the lock value, delay, and check it again before trying the lock.
  1001. Once the lock is acquired, however, the processor will proceed faster,
  1002. relatively unimpaired by other spinning processors.  
  1003. .PP
  1004. Even using this algorithm, there will be processor degradation when
  1005. there are large numbers of spinning processors.  When the lock
  1006. is released, every spinning processor encounters a cache miss.  After
  1007. this initial miss, most processors delay locally until some other processor
  1008. has acquired the lock, and then miss again to see that the lock has been
  1009. acquired.  With enough spinning processors, the bus can be saturated
  1010. with these misses, slowing down the processor executing in the critical 
  1011. section.
  1012. .PP
  1013. These cache misses can be avoided.  A processor can delay whenever it
  1014. reads the lock value as busy.  If the lock is not busy, the processor
  1015. can immediately try to acquire it.  Thus, spinning processors miss 
  1016. their cache every time the delay period expires,
  1017. rather than every time the lock is released.  This is analogous to the
  1018. Ethernet notion of persistence [Metcalfe & Boggs 1976].
  1019. A result of this variation is
  1020. an even greater delay between when a lock is released and when a spinning
  1021. processor will acquire the lock.
  1022. .PP
  1023. Processors can spin-wait (degrading other processors) for things
  1024. other than locks.  Agarwal and Cherian [1989] apply backoff
  1025. to spin-waiting for data to become available.
  1026. Spin-waiting can also be a problem with idle processors polling a central 
  1027. or distributed ready queue.  
  1028. When a ready thread is queued, if every idle processor rushes
  1029. to acquire the lock, bus saturation will result.  Even if each idle processor
  1030. delays after observing that a thread is queued, then makes sure that it 
  1031. is still queued, there is still a cache miss per idle processor,
  1032. hurting performance for large numbers of idle processors.
  1033. .PP
  1034. If idle processors are kept on a queue, this problem does not occur.
  1035. Each idle processor spins on a separate flag.  When a thread becomes ready to
  1036. run, only one processor's flag is modified; every other processor continues
  1037. spinning without even a cache miss.  The performance advantage of having
  1038. work look for processors instead of processors looking for work
  1039. will therefore be more important in systems with large numbers of processors.
  1040. This effect can be seen in Figure 3.4; the cost of the central ready queue
  1041. is higher when there are only a few runnable threads, since there are more
  1042. idle processors spin-waiting for work to appear in the ready queue.
  1043. .NH 2
  1044. Measurement results
  1045. .PP
  1046. Figure 4.1 shows the elapsed time for various numbers of processors
  1047. to cooperatively increment and test a shared counter
  1048. in a critical section 1 million times, for each method of spin-waiting.
  1049. Each processor executes a loop:  wait for the lock, increment the counter,
  1050. and release the lock.  
  1051. We do not claim that this test is representative
  1052. of the normal use of critical sections, but similar curves have been measured
  1053. with more significant computation between lock accesses.
  1054. Since there is little parallelism, if spinning 
  1055. processors did not slow the processor holding the lock, the curve 
  1056. would be flat.  
  1057. .PP
  1058. The magnitude of this effect is striking.  Both spinning on the
  1059. xchgb instruction and spinning on a read
  1060. degrade processor performance badly for even a moderate number of
  1061. spinning processors.  For small critical sections, in either alternative,
  1062. every spinning processor spends all of its time doing cache read misses
  1063. or atomic xchgb operations, consuming bus cycles as fast as possible.
  1064. By contrast, the backoff algorithm results in only slight degradation
  1065. unless the number of spin-waiting processors exceeds ten.
  1066. .KF
  1067. .sp 3.0i
  1068. .ce 2
  1069. \fBFigure 4.1:  Principal results for spin-waiting:  elapsed time
  1070. to increment a shared counter to 1,000,000 (measured)\fR
  1071. .sp 3.0i
  1072. .ce
  1073. \fBFigure 4.2:  Relative processor speed (8 processors to 1 processor) vs. critical section size (measured)\fR
  1074. .sp .5
  1075. .KE
  1076. .PP
  1077. Figure 4.2 shows the effect of increasing the size of the 
  1078. critical section on each algorithm's performance.  In addition to 
  1079. incrementing a counter, the critical section contains varying 
  1080. amounts of other work.  We normalized the time for eight processors
  1081. by the time for one processor.
  1082. This measures relative processor speed.
  1083. Again, if spin-waiting did not slow the 
  1084. processor holding the lock, one processor would not be faster than eight,
  1085. and the relative processor speed would always be equal to 1.
  1086. As expected, spinning on a read degrades performance less as 
  1087. the size of the critical section grows, while spinning on the xchgb 
  1088. instruction degrades performance evenly throughout the critical section.
  1089. .PP
  1090. To test the tradeoff between processor degradation and the delay
  1091. in acquiring a newly released lock, we measured the elapsed time 
  1092. for a number of processors to each increment a shared counter within 
  1093. a critical section.  Once a processor acquired the lock and bumped 
  1094. the counter once, it was set to loop until all processors were done.
  1095. This test is indicative of the cost of using a lock for barrier synchronization.
  1096. Figure 4.3 shows the elapsed time divided by the number of processors.
  1097. If there is no processor degradation or delay in acquiring the lock,
  1098. the elapsed time to achieve the barrier should increase linearly
  1099. with each additional processor; the normalized curve in Figure 4.3 should
  1100. be flat.
  1101. .KF
  1102. .sp 3.0i
  1103. .ce
  1104. \fBFigure 4.3:  Normalized time (\(*msec. per processor) to achieve barrier (measured)\fR
  1105. .sp .5
  1106. .KE
  1107. .PP
  1108. Figure 4.3 shows that for small numbers of processors,
  1109. spinning on the xchgb instruction is fastest, since a processor
  1110. immediately acquires the lock when it is released.  As more processors
  1111. are added, however, even though the lock is acquired faster, this
  1112. is outweighed by the degradation of the processor holding the lock.
  1113. The backoff algorithm shows a similar curve to spinning on a read,
  1114. but for a different reason.  Initially, many processors are queued for
  1115. the lock; this leads spinning processors to guess large delay times.
  1116. As more processors acquire the lock, there are fewer queued processors,
  1117. and the delays become inappropriate.
  1118. .PP
  1119. Processors doing work are slowed proportional to the number of times
  1120. they access the bus.  Thus, the results of these tests depend somewhat 
  1121. on the content of the critical section.  However, since the purpose of 
  1122. a critical section is to serialize modifications to shared data, its 
  1123. code is likely to be bus intensive.
  1124. Our measurements indicate that almost half of the bus service demand
  1125. of thread management is due to the critical section.
  1126. Further, thread management critical sections also tend to be small.
  1127. For example, enqueueing or dequeueing a ready thread in a critical
  1128. section both take less than 10 microseconds, roughly the same as for Figure 4.1.
  1129. .NH 2
  1130. Implications for other systems
  1131. .PP
  1132. In this section, we show that the performance of spin-waiting is of
  1133. concern on architectures other than the Symmetry Model A.
  1134. .PP
  1135. Some multiprocessors do not provide hardware cache coherency;
  1136. the BBN Butterfly [BBN 1985] is an example. 
  1137. For these systems, every test of a lock value by a spinning
  1138. processor requires a memory access.  By inserting a delay between each test,
  1139. the effect of spinning on busy processors can be reduced;
  1140. backoff can be used to adapt the frequency of reads to the number of 
  1141. waiting processors.
  1142. .PP
  1143. The Symmetry Model A has a write-through protocol:  when
  1144. a processor modifies a location, the value is written to memory and
  1145. all old copies of the location in other caches are invalidated.
  1146. There is a cost to spin-waiting, even in architectures with a write-back
  1147. cache coherency protocol.
  1148. In a write-back protocol, the value is stored in the cache and later
  1149. written to memory when the cache block is replaced.  There are two
  1150. major approaches to keeping other caches consistent with the new value:  all
  1151. old copies in other caches can either be invalidated or updated
  1152. with the new value (distributed-write) [Archibald & Baer 1986].
  1153. .PP
  1154. In the case of an invalidation-based write-back protocol, the spin-waiting
  1155. alternatives have much the same effect as with write-through.  If
  1156. processors spin on the atomic test-and-set operation, the valid copy
  1157. of the lock bounces from cache to cache, consuming bus resources.
  1158. Provided test-and-sets invalidate all cache copies whether or not
  1159. the lock value changes, spinning on a read does not help;
  1160. there is still a cascade of repeated invalidations when the lock is
  1161. released.
  1162. .PP
  1163. One possibility, then, is to add hardware to compare the old and
  1164. new value of the lock on a test-and-set and to invalidate other copies
  1165. only if they differ.  While this would improve the performance of
  1166. spinning on a read, it does not eliminate the problem.
  1167. With $P$ spinning processors, there are $O(P)$ bus requests
  1168. per lock acquisition.  Each processor must cache miss when the 
  1169. lock is released; it must also acquire the bus to ensure the 
  1170. atomicity of its subsequent test-and-set.
  1171. .PP
  1172. Systems with distributed-write coherency have similar performance.
  1173. When a processor performs an atomic operation, every cache with an 
  1174. old copy is updated with the new value; thus no cache misses occur.
  1175. If processors spin on a read, however, there will still be a 
  1176. rush of processors to try the lock when it is first released.
  1177. Since the backoff algorithm reduces the number of unsuccessful lock 
  1178. attempts, it would reduce the bus load due to spinning even further.
  1179. .PP
  1180. Explicitly queueing spinning processors can further improve performance.  Each
  1181. processor in the queue spins on a separate flag; when a processor
  1182. finishes with the lock, it passes control of the lock by setting the 
  1183. flag of the next one in the queue, without invalidating the flags
  1184. of the other waiting processors.
  1185. .PP
  1186. In a related paper, we devised an efficient queue-based spin-waiting 
  1187. algorithm that uses only $O(1)$ bus transactions per execution of the 
  1188. critical section [Anderson 1989].
  1189. Each arriving processor does an atomic read-and-increment
  1190. to obtain a unique sequence number.  When a processor finishes with
  1191. the lock, it taps the processor with the next highest sequence number;
  1192. that processor now owns the lock.
  1193. Since processors are sequenced, no atomic
  1194. read-then-write instruction is needed to pass control of the lock.
  1195. Table 4.1 lists the code for this approach ("myPlace" is a location private 
  1196. to each processor).  Measurements of this algorithm appear in that paper.
  1197. .KF
  1198. .sp 0.5
  1199. .ft C
  1200. .ps -1
  1201. .vs -1
  1202. .TS
  1203. center, box;
  1204. l | l.
  1205. Init    flags[0] = HAS_LOCK;
  1206.     flags[1..P-1] = MUST_WAIT;
  1207.     queueLast = 0;
  1208. _
  1209. Lock    myPlace = ReadAndIncrement(queueLast);
  1210.     while (flags[myPlace mod P] == MUST_WAIT)
  1211.       ;
  1212.     flags[myPlace mod P] = MUST_WAIT;
  1213. _
  1214. Unlock    flags[(myPlace + 1) mod P] = HAS_LOCK;
  1215. .TE
  1216. .ps +1
  1217. .vs +1
  1218. .ft P
  1219. .ce
  1220. \fBTable 4.1: Queue-based algorithm for spin-waiting
  1221. .sp 0.5
  1222. .KE
  1223. .NH
  1224. Analytical Results
  1225. .PP
  1226. We developed a simple queueing network model for our thread package
  1227. to demonstrate that the combination of processor degradation due 
  1228. to bus contention and the effect of lock contention can 
  1229. account for our measurements.  We then used the validated model to 
  1230. project the performance of our thread package under varying conditions.
  1231. .PP
  1232. Our model is hierarchical.  The low level model represents the
  1233. effect of bus contention on processor speed. The high level
  1234. model represents the effect of lock contention
  1235. on throughput and response time.  Since processor speed affects 
  1236. the amount
  1237. of lock contention and the number of spinning processors affects
  1238. bus contention and thus processor speed, we iterate between levels 
  1239. to convergence.  We describe the two sub-models in more detail below.
  1240. .NH 2
  1241. Modelling bus contention
  1242. .PP
  1243. In the low level model, we represent each processor as a customer
  1244. in a closed queueing network.  The network has two service centers:  a
  1245. queueing center for the bus and a delay center for non-bus activity.
  1246. Each processor spends some of its time referencing memory through the 
  1247. bus and thus contending with other processors also using the bus, 
  1248. and some of its time processing out of its cache, independent of the
  1249. activity of other processors.  Processor speed is degraded by the 
  1250. percentage of time spent queueing, but not in service, at the bus.  
  1251. .KF
  1252. .sp 2.0i
  1253. .ce
  1254. \fBDiagram 5.1:  Low level model of bus contention\fR
  1255. .sp .5
  1256. .KE
  1257. .PP
  1258. This model is an approximation of the real bus mechanism, which is 
  1259. considerably more complex [Lovett & Thakkar 1988].
  1260. At moderate loads, our model will be pessimistic by predicting more
  1261. contention than is actually experienced.  Because of the regularity
  1262. of the time each processor spends computing between accesses to the bus,
  1263. if two processors collide at the bus, they are unlikely to collide
  1264. at their next visit.  Our model assumes that arrivals are more nearly
  1265. independent.
  1266. .PP
  1267. There are three components to bus utilization.  A processor can
  1268. be executing user code, thread management code, or spin-waiting,
  1269. each with different service demands on the bus.  Given these
  1270. service demands and the ratio of time each processor spends in each 
  1271. type of activity, we determine the aggregate service demands
  1272. at the bus and at the delay center and use these aggregate demands 
  1273. to solve the model.
  1274. .PP
  1275. Since it is difficult to analytically determine the bus demand of a section
  1276. of code, we determine a portion of it inductively from measurements.
  1277. We provide each processor with its own copy of all data structures;
  1278. we then run the code in parallel on each processor.  Since there is no 
  1279. shared data, there can be no contention for software resources; any 
  1280. delay experienced by a processor relative to when it is running the 
  1281. code by itself must be due to contention for hardware resources, such as 
  1282. for memory or the bus.  We then match a curve from our model of the bus 
  1283. to the measured 
  1284. curve and use the result as the service demand for that section of code. 
  1285. The curves matched well in practice, deviating only at moderate loads,
  1286. as expected.
  1287. .PP
  1288. Since bus contention may disproportionately impact the critical section
  1289. execution time, affecting lock contention in the high level model, we 
  1290. used this approach separately for the critical section and 
  1291. non-critical section code within thread management.  The critical 
  1292. section code turns out to account for much of the bus demand of thread
  1293. management.
  1294. .PP
  1295. Even though it could affect bus usage, we did not include in our model 
  1296. the effect of different numbers of processors on cache hit ratios.
  1297. When a processor writes a location, the Symmetry updates both 
  1298. memory and that processor's cache.
  1299. As a result, on a single processor, data that is both written and 
  1300. read will tend to stay in the cache, avoiding cache misses.
  1301. When multiple processors read and write shared data, the cache copies
  1302. of the data will be repeatedly invalidated as different processors 
  1303. update it, resulting in more cache misses than in the single processor 
  1304. case.  Our model therefore underestimates bus demand, making it
  1305. optimistic, especially as the bus nears saturation.
  1306. .PP
  1307. The bus demand of spinning processors 
  1308. was also determined inductively.  $P$ processors were set to run
  1309. the critical section with separate copies of the data structures;
  1310. by the experiment described above, we know the bus service demand 
  1311. of these processors.  $Q$ processors were set to run a shared copy
  1312. of the critical section; one of these processors has the normal
  1313. bus service demand, and $Q~-~1$ spin-wait.  By measuring the
  1314. processor degradation of the $P$ copies, we can determine the
  1315. aggregate bus demand of the $Q~-~1$ spinning processors.  A two
  1316. class model was used, one class representing
  1317. processors executing critical sections and one representing
  1318. spinning processors.  Only the response time of the processors
  1319. executing the critical section is important.
  1320. .PP
  1321. The bus demand, at least for the backoff algorithm, is linear
  1322. with the number of processors.  While there is no \fIa priori\fR reason 
  1323. for this, it intuitively makes sense.  The effect of adding a spinning
  1324. processor with the backoff algorithm is to add two cache misses per 
  1325. execution of the critical section.  The bus demand of other processors
  1326. is relatively unaffected.  While this invariance would also hold
  1327. for the spin on xchgb algorithm, it is less true when processors spin
  1328. on memory reads, because the cascade of cache misses is longer for every
  1329. processor when more processors are spinning.  Note that the graphs in 
  1330. Section 4 could be used to infer the bus demand of spinning processors.
  1331. We did not choose this approach because there is a correlation 
  1332. between when the processor holding the lock and
  1333. when the processors spinning on the lock use the bus. 
  1334. The curve for the backoff algorithm in Figure 4.1, for example,
  1335. is similar to that of an optimistic asymptotic bound.
  1336. .NH 2
  1337. Modelling lock contention
  1338. .PP
  1339. In the high level model, we represent each lock in the thread management
  1340. path by a separate queueing center.  Processing time spent not holding 
  1341. a lock is modelled as a delay center.  Service demands were directly
  1342. measured, then the part of each service demand due to bus accesses
  1343. was inflated by the bus response time of the low level model.
  1344. As in the low level model, each processor is represented as a single 
  1345. customer in a closed class.
  1346. By solving this model, we can determine the average amount of time 
  1347. each processor spends spin-waiting for a lock versus executing thread 
  1348. operations or user code.  This ratio is then used as an input to
  1349. the low level model.
  1350. (Note that it is a simple matter in this model 
  1351. to add queueing centers if the application-level code does further locking.)
  1352. .KF
  1353. .sp 2.0i
  1354. .ce
  1355. \fBDiagram 5.2:  High level model of lock contention for the local freelist alternative\fR
  1356. .sp .5
  1357. .KE
  1358. .PP
  1359. If the time between thread operations is deterministic, our model
  1360. is pessimistic at moderate loads.  As for the bus, if two processors
  1361. collide at a lock, the effect of deterministic processing times is to
  1362. reduce the likelihood that they will collide at the next visit.
  1363. Figure 3.2 shows this effect.  The curves are similar in shape to 
  1364. asymptotic optimistic bounds, since the processing time to do each 
  1365. thread operation is deterministic.  Figure 3.3 does not show this effect,
  1366. since the user computation for each thread was randomly chosen from a 
  1367. uniform distribution.
  1368. .PP
  1369. Our model does not explicitly represent an application's distribution 
  1370. of parallelism, although Figure 3.4 shows that this affects performance.
  1371. We chose not to include this in our model since the distribution,
  1372. and more importantly the effect of lock queueing delay on that
  1373. distribution, are almost always application-dependent.
  1374. .PP
  1375. Given the distribution, the model could be evaluated separately for each 
  1376. population of threads; these separate evaluations could then be 
  1377. averaged, weighted by the proportion of time for that population.
  1378. The population of the high level model should be the minimum between the
  1379. number of processors and the number of threads, reflecting the number
  1380. of active processors.  The population of the low level model should
  1381. be set similarly, except that since idle processors consume bus resources,
  1382. a second class should be added to represent them. 
  1383. .PP
  1384. This method of separate evaluations ignores the fact that lock 
  1385. contention can only occur when the parallelism is being incremented or 
  1386. decremented; we believe that any distortion introduced by the adaptive 
  1387. nature of the mechanism will be outweighed by the effects of lock and
  1388. bus contention.  Ni and Wu [1985] also discuss this issue.
  1389. .NH 2
  1390. Comparison with measured results, and projections
  1391. .PP
  1392. Figure 5.1 compares our model results with our measurement results
  1393. previously reported in Figure 3.3.  We modelled two
  1394. alternatives:  per-processor ready queues (local readyq) and 
  1395. per-processor free lists with a central ready queue (local freelist).
  1396. Our model agrees well with the measurements, within 5% except
  1397. for the central ready queue with 18 processors.  The model predicts
  1398. the shape of the curve, but is somewhat optimistic; this appears to
  1399. be due to underestimating the bus demand, which is important in
  1400. determining the effective size of the critical section.  The model
  1401. does capture the difference between the alternatives.
  1402. .PP
  1403. Having validated our model, we used it to investigate the effect
  1404. of varying key parameters.  Figure 5.2 shows speedup of a hypothetical 
  1405. application with 20 processors
  1406. as a function of the amount of user computation per thread.  As we 
  1407. would expect, as an application uses finer-grained 
  1408. parallelism (smaller amounts of computation per thread), the central
  1409. lock on the ready queue becomes a bottleneck.
  1410. For sufficiently
  1411. coarse-grained parallelism, the performance of the thread package ceases 
  1412. to matter.  In the limit, even DYNIX processes could be used.
  1413. .KF
  1414. .sp 3.0i
  1415. .ce
  1416. \fBFigure 5.1:  Comparison of analytic and measured results from Figure 3.3\fR
  1417. .KE
  1418. .KF
  1419. .sp 3.0i
  1420. .ce 2
  1421. \fBFigure 5.2:  Speedup vs. \(*msec. of user computation
  1422. per thread, 20 processors, bus load = 5% (analytic)\fR
  1423. .sp .5
  1424. .KE
  1425. .KF
  1426. .sp 3.0i
  1427. .ce
  1428. \fBFigure 5.3:  Speedup vs. bus load, user work = 200 \(*msec., 20 processors (analytic)\fR
  1429. .sp .5
  1430. .KE
  1431. .PP
  1432. Contention for the bus can also reduce the difference between the
  1433. alternatives.  Figure 5.3 shows speedup as a function of the
  1434. percentage usage of the bus by each thread.  As the bus usage increases,
  1435. the bus limits the speedup with local ready queues, but
  1436. it also limits the speedup with the central ready queue, since
  1437. bus contention inflates the critical section time.
  1438. .PP
  1439. On the other hand, the central ready queue lock can again limit speedup 
  1440. even for more coarsely-grained parallelism, given a sufficient
  1441. number of processors.  Figure 5.4 shows speedup as a function
  1442. of the number of processors when threads each compute for 2 milliseconds.
  1443. The sharp dropoff for the central ready queue alternative shows the 
  1444. inherent instability of a system where spinning processors consume resources.
  1445. .KF
  1446. .sp 3.0i
  1447. .ce
  1448. \fBFigure 5.4:  Speedup vs. number of processors, user work = 2 msec. (analytic)\fR
  1449. .sp
  1450. .KE
  1451. .bp
  1452. .NH
  1453. Conclusions
  1454. .PP
  1455. Threads have become a common element of new languages and operating systems.
  1456. Efficient thread management is critical to achieving good performance
  1457. from parallel applications.  We have studied the 
  1458. performance
  1459. implications of several thread management and locking alternatives.
  1460. We showed that:
  1461. .IP \(bu
  1462. It is possible to implement a fast thread package.  Simplicity is
  1463. crucial for this.
  1464. .IP \(bu
  1465. For fine-grained parallelism, small changes in data structures
  1466. and locking have a significant effect on both latency and throughput.
  1467. .IP \(bu
  1468. Per-processor data structures can be used to improve throughput;
  1469. if a resource is not scarce, localizing data can avoid locking,  
  1470. improving latency as well.
  1471. .IP \(bu
  1472. Spin-waiting can delay not only the processor waiting for a lock, 
  1473. but other processors doing work.  This appears to be independent
  1474. of the cache coherency protocol.
  1475. .IP \(bu
  1476. The cost of spin-waiting can be reduced by using an
  1477. Ethernet-style backoff or a queue-based algorithm.
  1478. .IP \(bu
  1479. A simple queueing model can accurately predict the effect of a combination 
  1480. of factors on the performance of shared-memory multiprocessors.
  1481. .PP
  1482. An area of future research is to determine the extent to which our results,
  1483. developed in the context of thread management systems,
  1484. also apply to application programs that exploit fine-grained parallelism
  1485. on shared-memory multiprocessors.
  1486. .SH
  1487. Acknowledgements
  1488. .PP
  1489. We would like to thank Dave Wagner for suggesting that an Ethernet-style
  1490. algorithm might solve the spin-waiting problem.  A preliminary version of 
  1491. this paper appeared in the Proceedings
  1492. of the 1989 ACM SIGMETRICS and Performance '89 International
  1493. Conference on Measurement and Modeling of Computer Systems,
  1494. \fIPerformance Evaluation Review 17,\fR1 (May 1989),
  1495. Copyright 1989, Association for Computing Machinery, Inc.,
  1496. reprinted by permission.
  1497. .SH
  1498. References
  1499. .nh
  1500. .ps 11
  1501. .nr PS 11
  1502. .vs 12
  1503. .nr VS 12
  1504. .LP
  1505. [Accetta et al. 1986]
  1506. .RS
  1507. M. Accetta, R. Baron, W. Bolosky, D. Golub, R. Rashid, A. Tevanian,
  1508. and M. Young.
  1509. Mach:  A New Kernel Foundation For UNIX Development.
  1510. \fIProc. Summer 1986 USENIX Technical Conference and Exhibition\fR,
  1511. June 1986, pp. 93-112.
  1512. .RE
  1513. .LP
  1514. [Agarwal & Cherian 1989]
  1515. .RS
  1516. A. Agarwal and M. Cherian.
  1517. Adaptive Backoff Synchronization Techniques.
  1518. \fIProc. 16th International Symposium on Computer Architecture\fR,
  1519. June, 1989, pp. 396-406.
  1520. .RE
  1521. .LP
  1522. [Anderson 1989]
  1523. .RS
  1524. Thomas E. Anderson.  The Performance Implications of Spin-Waiting Alternatives
  1525. for Shared-Memory Multiprocessors.
  1526. \fIProc. 1989 International Conference on Parallel Processing\fR, Aug. 1989.
  1527. .RE
  1528. .LP
  1529. [Archibald & Baer 1986]
  1530. .RS
  1531. J. Archibald and J.-L. Baer.
  1532. Cache Coherence Protocols:  Evaluation Using a Multiprocessor
  1533. Simulation Model.
  1534. \fIACM Transactions on Computer Systems\fR, vol. 4, no. 4, Nov. 1986,
  1535. pp. 273-298.
  1536. .RE
  1537. .LP
  1538. [Bach & Buroff 1984]
  1539. .RS
  1540. M. J. Bach and S. J. Buroff.
  1541. Multiprocessor UNIX Operating Systems.
  1542. \fIAT&T Bell Laboratories Technical Journal\fR, 
  1543. vol. 63, no. 8, Oct. 1984, pp. 1733-1749.
  1544. .RE
  1545. .LP
  1546. [BBN 1985]
  1547. .RS
  1548. BBN Laboratories.  Butterfly Parallel Processor Overview. 1985.
  1549. .RE
  1550. .LP
  1551. [Bershad et al. 1988a]
  1552. .RS
  1553. Brian Bershad, Edward Lazowska, and Henry Levy.
  1554. Presto:  A System for Object-Oriented Parallel Programming.
  1555. \fISoftware:  Practice
  1556. and Experience\fR, vol. 18, no. 8, Aug. 1988, pp. 713-732.
  1557. .RE
  1558. .LP
  1559. [Bershad et al. 1988b]
  1560. .RS
  1561. Brian Bershad, Edward Lazowska, Henry Levy, and David Wagner.
  1562. An Open Environment for Building Parallel Programming Systems.
  1563. \fIProc. ACM/SIGPLAN PPEALS 1988\fR, pp. 1-9. 
  1564. .RE
  1565. .LP
  1566. [Dritz & Boyle 1987]
  1567. .RS
  1568. Kenneth W. Dritz and James M. Boyle.
  1569. Beyond "Speedup":  Performance Analysis
  1570. of Parallel Programs.
  1571. Technical Report ANL-87-7, Mathematics and
  1572. Computer Science Division, Argonne National Laboratory,
  1573. Feb. 1987.
  1574. .RE
  1575. .LP
  1576. [Eager et al. 1986]
  1577. .RS
  1578. Derek Eager, Edward Lazowska, and John Zahorjan.
  1579. Adaptive Load Sharing in Homogeneous Distributed Systems.
  1580. \fIIEEE Transactions on Software Engineering\fR, 
  1581. vol. 12, no. 5, May 1986, pp. 662-675.
  1582. .RE
  1583. .LP
  1584. [Edler et al. 1988]
  1585. .RS
  1586. Jan Edler, Jim Lipkis, and Edith Schonberg.
  1587. Process Management for Highly Parallel UNIX Systems.
  1588. Ultracomputer Note #136, April 1988.
  1589. .RE
  1590. .LP
  1591. [Hoare 1978]
  1592. .RS
  1593. C.A.R. Hoare.
  1594. Communicating Sequential Processes.
  1595. \fICommunications of the ACM\fR, vol. 21, no. 8, Aug. 1978, pp. 666-677.
  1596. .RE
  1597. .LP
  1598. [Holt 1982]
  1599. .RS
  1600. R. Holt.
  1601. A Short Introduction to Concurrent Euclid.
  1602. \fISIGPLAN Notices\fR, vol. 17, May 1982, pp. 60-79.
  1603. .RE
  1604. .LP
  1605. [Jul et al. 1988]
  1606. .RS
  1607. Eric Jul, Henry Levy, Norman Hutchinson, and Andrew Black.
  1608. Fine-Grained Mobility in the Emerald System.
  1609. \fIACM Transactions on Computer Systems\fR, vol. 6, no. 1, Feb. 1988,
  1610. pp. 109-133.
  1611. .RE
  1612. .LP
  1613. [Kumar & Gonsalves 1977]
  1614. .RS
  1615. B. Kumar and Timothy Gonsalves.
  1616. Modelling and Analysis of Distributed Software Systems.
  1617. \fIProc. 7th ACM Symposium on Operating Systems Principles\fR,
  1618. Dec. 1977, pp. 2-8.
  1619. .RE
  1620. .LP
  1621. [Lampson & Redell 1980]
  1622. .RS
  1623. B.W. Lampson and D.D. Redell.
  1624. Experiences with Processes and Monitors in Mesa.
  1625. \fICommunications of the ACM\fR, vol. 23, no. 2, Feb. 1980, pp. 104-117.
  1626. .RE
  1627. .LP
  1628. [Lovett & Thakkar 1988]
  1629. .RS
  1630. Tom Lovett and Shreekant Thakkar.
  1631. The Symmetry Multiprocessor System.
  1632. \fIProc. 1988 International Conference on Parallel Processing\fR,
  1633. pp. 303-310. 
  1634. .RE
  1635. .LP
  1636. [Metcalfe & Boggs 1976]
  1637. .RS
  1638. Robert Metcalfe and David Boggs.
  1639. Ethernet:  Distributed Packet Switching for Local Computer Networks.
  1640. \fICommunications of the ACM\fR, vol. 19, no. 7, July 1976, pp. 395-404.
  1641. .RE
  1642. .LP
  1643. [Mundie & Fisher 1985]
  1644. .RS
  1645. D.A. Mundie and D.A. Fisher.
  1646. Parallel Processing in Ada.
  1647. \fIIEEE Computer\fR, Aug. 1985, pp. 20-25.
  1648. .RE
  1649. .LP
  1650. [Ni & Wu 1985]
  1651. .RS
  1652. Lionel Ni and Ching-Fern Wu.
  1653. Design Trade-offs for Process Scheduling in Tightly Coupled Multiprocessor
  1654. Systems.
  1655. \fIIEEE Transactions on Software Engineering\fR, vol. 15, no. 3, March 1989,
  1656. pp. 327-334. 
  1657. .RE
  1658. .LP
  1659. [Scott et al. 1988]
  1660. .RS
  1661. Michael Scott, Thomas LeBlanc, and Brian Marsh.
  1662. Design Rationale for
  1663. Psyche, a General Purpose Multiprocessor Operating System.
  1664. \fIProc. 1988 International Conference on Parallel Processing\fR,
  1665. August, 1988.
  1666. .RE
  1667. .LP
  1668. [Sequent 1988]
  1669. .RS
  1670. Sequent Computer Systems, Inc.
  1671. Symmetry Technical Summary.
  1672. .RE
  1673. .LP
  1674. [Thacker et al. 1988]
  1675. .RS
  1676. Charles Thacker, Lawrence Stewart, and Edward Satterthwaite Jr.
  1677. Firefly:  A Multiprocessor Workstation.
  1678. \IEEE Transactions on Computers\fR, vol. 37, no. 8, Aug. 1988, pp. 909-920.
  1679. .RE
  1680. .LP
  1681. [Vandevoorde & Roberts 1988]
  1682. .RS
  1683. Mark Vandevoorde and Eric Roberts.
  1684. WorkCrews:  An Abstraction for Controlling Parallelism.
  1685. \fIInternational Journal of Parallel Programing\fR, vol. 17, no. 4,
  1686. Aug. 1988, pp. 347-366.
  1687. .RE
  1688. .LP
  1689. [Wagner et al. 1989]
  1690. .RS
  1691. David Wagner, Edward Lazowska, and Brian Bershad.
  1692. Techniques for Efficient Shared-Memory Parallel Simulation.
  1693. \fIDistributed Simulation 1989\fR, Society for Computer Simulation,
  1694. March 1989, pp. 29-37.
  1695. .RE
  1696. .LP
  1697. [Zahorjan et al. 1988]
  1698. .RS
  1699. John Zahorjan, Edward Lazowska, and Derek Eager.
  1700. Spinning Versus Blocking in Parallel Systems with Uncertainty.
  1701. \fIProc. International Seminar on the Performance of 
  1702. Distributed and Parallel Systems\fR, North Holland, Dec. 1988.
  1703. .RE
  1704.